package org.class2table.sync;

import cn.hutool.core.collection.CollectionUtil;
import org.class2table.core.Class2TableException;
import org.class2table.core.clazz.bean.ClazzMetaData;
import org.class2table.core.clazz.bean.PropertyMetaData;
import org.class2table.core.parent.bean.PrimaryKeyInfo;
import org.class2table.core.table.bean.ColumnMetaData;
import org.class2table.core.table.bean.TableMetaData;
import org.class2table.core.clazz.ClazzProcessor;
import org.class2table.core.table.TableProcessor;
import org.class2table.core.dictionary.Constants;
import org.class2table.core.util.SqlBuildUtil;
import org.class2table.sync.bean.CompareResult;
import org.class2table.sync.bean.ExeSqlInfo;
import org.class2table.sync.bean.TableResult;

import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

public class CompareProcessor {

    //待同步类型
    private static List<Class<?>> tableClasses;

    //主库
    private static Connection masterConnection;

    //租户数据库名字
    private static Set<String> tenantDbNames;

    //规则map
    private static Map<Class<?>, String> clazz2ColumnTypeRuleMap;

    public static void init(Connection masterConnection, Set<String> tenantDbNames, List<Class<?>> tableClasses, Map<Class<?>, String> clazz2ColumnTypeRuleMap) {

        if (CollectionUtil.isEmpty(tableClasses)) {
            throw new Class2TableException("CompareProcessor init failed! tableClasses is empty!");
        }
        if (Objects.isNull(masterConnection)) {
            throw new Class2TableException("CompareProcessor init failed! masterConnection is null!");
        }
        if (CollectionUtil.isEmpty(tenantDbNames)) {
            throw new Class2TableException("CompareProcessor init failed! tenantDbNames is empty!");
        }
        if (CollectionUtil.isEmpty(clazz2ColumnTypeRuleMap)) {
            throw new Class2TableException("CompareProcessor init failed! clazz2ColumnTypeRuleMap is empty!");
        }

        CompareProcessor.masterConnection = masterConnection;
        CompareProcessor.tableClasses = tableClasses;
        CompareProcessor.tenantDbNames = tenantDbNames;
        CompareProcessor.clazz2ColumnTypeRuleMap = clazz2ColumnTypeRuleMap;
    }

    public static List<CompareResult> compare() {
        List<CompareResult> compareResultList = new ArrayList<>();
        for (String dbName : tenantDbNames) {
            CompareResult compareResult = new CompareResult();
            compareResult.setDbName(dbName);

            List<TableResult> tableResultList = new ArrayList<>();

            for (Class<?> tableClass : tableClasses) {
                TableResult tableResult = new TableResult();

                ClazzMetaData clazzMetaData = ClazzProcessor.getClassMetaData(tableClass, clazz2ColumnTypeRuleMap);

                TableMetaData tableMetaData;
                try {
                    tableMetaData = TableProcessor.getTableMetaData(masterConnection,dbName, clazzMetaData.getTableName());
                } catch (Exception e) {
                    throw new Class2TableException(String.format("compare failed! getTableMetaData of [%s %s] failed!", dbName, clazzMetaData.getTableName()),e);
                }

                List<ExeSqlInfo> exeSqlInfos = generateExeSqlInfosByMetaData(tableMetaData, clazzMetaData);

                tableResult.setTableName(clazzMetaData.getTableName());
                tableResult.setExeSqlInfos(exeSqlInfos);

                tableResultList.add(tableResult);
            }
            compareResult.setTableResults(tableResultList);
            compareResultList.add(compareResult);
        }
        return compareResultList;
    }

    private static List<ExeSqlInfo> generateExeSqlInfosByMetaData(TableMetaData tableMetaData, ClazzMetaData clazzMetaData) {

        List<ExeSqlInfo> exeSqlInfoList = new ArrayList<>();

        if (Objects.isNull(tableMetaData)) {
            String createTableSql = SqlBuildUtil.buildCreateTableSql(clazzMetaData);
            ExeSqlInfo exeSqlInfo = new ExeSqlInfo();
            exeSqlInfo.setSqls(Collections.singletonList(createTableSql));
            exeSqlInfo.setOperationType(Constants.OperationType.CREATE_TABLE);
            exeSqlInfoList.add(exeSqlInfo);
        } else {
            List<ColumnMetaData> columnMetaDataList = tableMetaData.getEntityAttrMetaDataList();
            List<PropertyMetaData> propertyMetaDataList = clazzMetaData.getEntityAttrMetaDataList();

            Boolean isPrimaryKeySame = compareTablePrimaryKey(tableMetaData.getPrimaryKeyInfoList(), clazzMetaData.getPrimaryKeyInfoList());
            if (!isPrimaryKeySame) {
                if (CollectionUtil.isNotEmpty(tableMetaData.getPrimaryKeyInfoList())) {
                    String dropPrimaryKeySql = SqlBuildUtil.buildDropPrimaryKeySql(clazzMetaData.getTableName());
                    ExeSqlInfo exeSqlInfo = new ExeSqlInfo();
                    exeSqlInfo.setSqls(Collections.singletonList(dropPrimaryKeySql));
                    exeSqlInfo.setOperationType(Constants.OperationType.DROP_PRIMARY_KEY);
                    exeSqlInfoList.add(exeSqlInfo);
                }
            }

            Map<String, ColumnMetaData> columnNameToColumnMetaData
                    = columnMetaDataList.stream().collect(Collectors.toMap(ColumnMetaData::getColumnName, o -> o));
            Set<String> columnNames = columnNameToColumnMetaData.keySet();
            Map<String, PropertyMetaData> propertyNameToPropertyMetaData
                    = propertyMetaDataList.stream().collect(Collectors.toMap(PropertyMetaData::getColumnName, o -> o));
            Set<String> propertyNames = propertyNameToPropertyMetaData.keySet();
            for (String propertyName : propertyNames) {
                ColumnMetaData columnMetaData = columnNameToColumnMetaData.get(propertyName);
                PropertyMetaData propertyMetaData = propertyNameToPropertyMetaData.get(propertyName);

                if (!columnNames.contains(propertyName)) {
                    String addColumnSql = SqlBuildUtil.buildAddColumnSql(clazzMetaData.getTableName(), propertyMetaData);
                    ExeSqlInfo exeSqlInfo = new ExeSqlInfo();
                    exeSqlInfo.setSqls(Collections.singletonList(addColumnSql));
                    exeSqlInfo.setOperationType(Constants.OperationType.ADD_COLUMN);
                    exeSqlInfoList.add(exeSqlInfo);
                    continue;
                }
                if (!compareColumnMetaData(columnMetaData, propertyMetaData)) {
                    String modifyColumnSql = SqlBuildUtil.buildModifyColumnSql(clazzMetaData.getTableName(), propertyMetaData);
                    ExeSqlInfo exeSqlInfo = new ExeSqlInfo();
                    exeSqlInfo.setSqls(Collections.singletonList(modifyColumnSql));
                    exeSqlInfo.setOperationType(Constants.OperationType.MODIFY_COLUMN);
                    exeSqlInfoList.add(exeSqlInfo);
                }
            }
            for (String dbColumnName : columnNames) {
                if (!propertyNames.contains(dbColumnName)) {
                    String dropColumnSql = SqlBuildUtil.buildDropColumnSql(clazzMetaData.getTableName(), dbColumnName);
                    ExeSqlInfo exeSqlInfo = new ExeSqlInfo();
                    exeSqlInfo.setSqls(Collections.singletonList(dropColumnSql));
                    exeSqlInfo.setOperationType(Constants.OperationType.DROP_COLUMN);
                    exeSqlInfoList.add(exeSqlInfo);
                }
            }

            if (!isPrimaryKeySame) {
                if (CollectionUtil.isNotEmpty(clazzMetaData.getPrimaryKeyInfoList())) {
                    String addPrimaryKeySql = SqlBuildUtil.buildAddPrimaryKeySql(clazzMetaData.getTableName(), clazzMetaData.getPrimaryKeyInfoList());
                    ExeSqlInfo exeSqlInfo = new ExeSqlInfo();
                    exeSqlInfo.setSqls(Collections.singletonList(addPrimaryKeySql));
                    exeSqlInfo.setOperationType(Constants.OperationType.ADD_PRIMARY_KEY);
                    exeSqlInfoList.add(exeSqlInfo);
                }
            }

        }
        return exeSqlInfoList;
    }

    private static Boolean compareTablePrimaryKey(List<PrimaryKeyInfo> tablePrimaryKeyInfoList, List<PrimaryKeyInfo> clazzPrimaryKeyInfoList) {
        if (tablePrimaryKeyInfoList.size() != clazzPrimaryKeyInfoList.size()) {
            return false;
        }
        for (int i = 0; i < tablePrimaryKeyInfoList.size(); i++) {
            PrimaryKeyInfo tablePrimaryKeyInfo = tablePrimaryKeyInfoList.get(i);
            PrimaryKeyInfo clazzPrimaryKeyInfo = clazzPrimaryKeyInfoList.get(i);
            if (!tablePrimaryKeyInfo.getColumnName().equals(clazzPrimaryKeyInfo.getColumnName())) {
                return false;
            }
        }
        return true;
    }

    private static Boolean compareColumnMetaData(ColumnMetaData columnMetaData, PropertyMetaData propertyMetaData) {
        boolean isSame = propertyMetaData.getColumnType().equals(columnMetaData.getColumnType())
                && propertyMetaData.getIsNullable().equals(columnMetaData.getIsNullable())
                && propertyMetaData.getColumnComment().equals(columnMetaData.getColumnComment());
        if (propertyMetaData.getIsSetDefault()) {
            isSame = isSame && propertyMetaData.getColumnDefault().equals(columnMetaData.getColumnDefault());
        }
        return isSame;
    }

}